package com.klikmee.sms.core.base.communicator.starion;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.log4j.Logger;

import com.klikmee.sms.cef.bulk.BulkSender;
import com.klikmee.sms.cef.bulk.BulkSender.BulkParams;
import com.klikmee.sms.cef.bulk.SendBulkException;
import com.klikmee.sms.core.base.Core;
import com.klikmee.sms.core.base.communicator.DeliveryReport;
import com.klikmee.sms.core.base.communicator.SendSmsIntercepter;
import com.klikmee.sms.core.base.dao.ProviderDAO;
import com.klikmee.sms.core.base.util.Constants;
import com.klikmee.sms.core.base.util.DeliveryReports;
import com.klikmee.sms.core.commons.exceptions.ExceptionHandler;
import com.klikmee.sms.core.model.BulkMessage;
import com.klikmee.sms.core.model.IncomingMessage;
import com.klikmee.sms.core.model.OutgoingMessage;
import com.klikmee.sms.core.model.Provider;

/**
 * Implementation of the starion communicator, responsible for sending and
 * receiving messages over starion provider. *
 * 
 * @author makcro
 * 
 */
public class StarionCommunicatorImpl implements StarionCommunicator {

	/**
	 * Logger instance
	 */
	private Logger						logger				= Logger.getLogger( StarionCommunicatorImpl.class );

	/**
	 * Instance of core context
	 */
	private Core						core				= null;

	/**
	 * URL of Starion's server used to send sms messages
	 */
	private String						smsCenterUrl		= null;

	/**
	 * List of send SMS intercepters for this communicator
	 */
	private List<SendSmsIntercepter>	smsIntercepters;

	/**
	 * Instance of starton provider
	 */
	private Provider					startionProvider;

	private ProviderDAO					providerDAO			= null;

	/**
	 * Instance of default exception handler
	 */
	private ExceptionHandler			exceptionHandler	= null;

	/**
	 * Starion implementation of bulk sender
	 */
	private BulkSender<String>			bulkSender;

	/**
	 * Username for starion sms platform used to send bulk messages
	 */
	private String						username;

	/**
	 * Regular expression used to check if this communicator can sent bulk to
	 * specific MSISDN
	 */
	private String						bulkReceiversRegEx;

	public void init() {
		this.startionProvider = providerDAO.findByName( Constants.PROVIDER_STARION );
		logger.info( "Staroin communicator initialized." );
	}

	/**
	 * Sends message via Starion provider. Utilizes http API for sending
	 * messages. After successful sending,
	 * {@link Core#onMessageSent(OutgoingMessage)} call back is invoked.
	 * 
	 * @param outgoingMessage
	 *            outgoing message to be sent
	 */
	@Override
	public void sendMessage(OutgoingMessage outgoingMessage) {
		HttpClient httpClient = new HttpClient();
		HttpMethod httpMethod = new GetMethod( smsCenterUrl );

		try {

			NameValuePair[] queryStringParams = assembleQueryStringParams( outgoingMessage );
			httpMethod.setQueryString( queryStringParams );
			logger.debug( MessageFormat.format( "Trying to execute http method [{0}]", httpMethod.getQueryString() ) );
			int result = httpClient.executeMethod( httpMethod );

			if (result != HttpStatus.SC_OK) {
				logger.fatal( MessageFormat.format( "Invalid http status while sending message id = [{0}]", outgoingMessage.getId() ) );
			}

			String response = httpMethod.getResponseBodyAsString();

			if (response.equals( SendSmsResponse.OK.getValue() )) {
				core.onMessageSent( outgoingMessage );
				logger.info( MessageFormat.format( "Successfully sent message with id [{0}] and got response [{1}]", outgoingMessage.getId(), response ) );
			}
			else {
				logger.fatal( MessageFormat.format( "Failed to send message with starion response [{0}]", response ) );
			}

		}
		catch (HttpException e) {
			this.exceptionHandler.handle( e );
		}
		catch (IOException e) {
			this.exceptionHandler.handle( e );
		}
		finally {
			httpMethod.releaseConnection();
		}
	}

	/**
	 * Creates parameters for send SMS query string by mapping
	 * {@link OutgoingMessage} properties to name-value pairs.
	 * 
	 * @param outgoingMessage
	 *            outgoing message that is being sent
	 * @return an array of {@link NameValuePair} representing message parameter
	 *         and it's values.
	 * @throws UnsupportedEncodingException
	 * @throws URIException
	 */
	private NameValuePair[] assembleQueryStringParams(OutgoingMessage outgoingMessage) throws UnsupportedEncodingException {
		NameValuePair[] result = new NameValuePair[4];
		result[0] = new NameValuePair( SendSmsParams.MSG_ID.getValue(), outgoingMessage.getProvidersMesssageId() );
		// result[1] = new NameValuePair(SendSmsParams.MESSAGE.getValue(),
		// URLEncoder.encode(outgoingMessage.getMessage(), "UTF-8"));
		result[1] = new NameValuePair( SendSmsParams.MESSAGE.getValue(), outgoingMessage.getMessage() );

		result[2] = new NameValuePair( SendSmsParams.CHARGE.getValue(), outgoingMessage.getPaymentInvoiceList().iterator().next().getServiceChargingCategory()
				.getTelenorChargingCode() );

		result[3] = new NameValuePair( SendSmsParams.TYPE.getValue(), MessageType.TEXT.getValue() );

		return result;
	}

	@Override
	public void sendBulkMessage(BulkMessage bulkMessage) {
		// construct table of bulk params from bulk message
		Hashtable<BulkParams, Object> params = new Hashtable<BulkSender.BulkParams, Object>();
		params.put( BulkParams.CORE_BULK_MESSAGE_ID, bulkMessage.getId() );
		params.put( BulkParams.RECEIVERS, bulkMessage.getToNumber() );
		params.put( BulkParams.SENDER, bulkMessage.getSenderId() );
		// params.put( BulkParams.SEQ_ID, UUID.randomUUID().toString() );
		params.put( BulkParams.SEQ_ID, "klikmee" + bulkMessage.getId() );
		params.put( BulkParams.TEXT, bulkMessage.getText() );
		params.put( BulkParams.USERNAME, this.username );
		params.put( BulkParams.REQUEST_DELIVERY_REPORT, bulkMessage.getChargingCategory().getTelenorChargingCode() );

		try {

			String httpResponse = this.bulkSender.send( params );
			// parse response
			BulkStatus bulkStatus = BulkResponseParser.parse( httpResponse );
			// update message status
			bulkMessage.setPrividersId( bulkStatus.getStartionMessageId() );
			logger.info( MessageFormat.format( "Bulk message id [{0}], text [{1}] successfuly sent with providers id[{2}]", bulkMessage.getId(), bulkMessage.getText(),bulkMessage.getPrividersId() ) );

			core.onBulkMessageSent( bulkMessage );
		}
		catch (SendBulkException e) {
			this.exceptionHandler.handle( e );
		}
	}

	@Override
	public List<SendSmsIntercepter> getSendSmsIntercepters() {
		return smsIntercepters;
	}

	/**
	 * Called when new sms message request is received over
	 * {@link SmsRequestHandlerImpl} instance. Constructs
	 * {@link IncomingMessage} and enqueues it for processing by calling
	 * {@link Core#enqueueIncomingMessage(IncomingMessage)}
	 */
	@Override
	public void onSmsMessage(String senderId, String shortId, String providerId, String messageText, String starionMessageId, String sdp, String operator) {

		// create incoming message instance
		IncomingMessage message = new IncomingMessage();
		message.setFromNumber( senderId );
		message.setMessage( messageText );
		message.setToNumber( shortId );
		message.setProviderMessageId( starionMessageId );
		message.setProvider( startionProvider );
		message.setOurIncomingTime( new Date() );
		message.setTypeOfMessage( Constants.MESSAGE_TYPE_SMS );
		core.enqueueIncomingMessage( message );

		logger.info( String.format( "Forwarded new message request to core with providers msg id = [%s]", starionMessageId ) );

	}

	@Override
	public void onDeliveryReport(String startionMessageId, String charged) {
		DeliveryReport message = new DeliveryReport();

		// resolve delivery report value
		int deliveryReport = resolveDeliveryReport( charged );

		message.setProvidersMessageId( startionMessageId );
		message.setStatus( deliveryReport );
		message.setProvider( startionProvider );
		logger.info( MessageFormat.format( "Resolved starion status [{0}] to core status [{1}] for message id [{2}], forwarding to core", charged,
				deliveryReport, startionMessageId ) );

		core.enqueueStatusReport( message );
	}

	@Override
	public Provider getProvider() {
		return this.startionProvider;
	}

	@Override
	public void onBulkDeliveryReport(String starionMessageId, String delivered, String description) {

		DeliveryReport deliveryReport = new DeliveryReport();
		int resolvedStatus = resolveBulkDeliveryReport( delivered );

		deliveryReport.setStatus( resolvedStatus );
		deliveryReport.setProvidersMessageId( starionMessageId );
		deliveryReport.setProvider( this.startionProvider );

		logger.info( MessageFormat.format( "Received status report for bulk message[{0}], status[{1}], desc[{2}], resolved to [{3}]", starionMessageId,
				delivered, description, resolvedStatus ) );

		core.enqueueStatusReport( deliveryReport );
	}

	private int resolveBulkDeliveryReport(String value) {
		if (value.equals( StarionBulkDeliveryReportValues.DELIVERED.getValue() )) {
			return DeliveryReports.DELIVERED;
		}
		else {
			return DeliveryReports.Starion.UN_DELIVERED;
		}
	}

	/**
	 * Resolves starion's delivery report value against core context
	 * 
	 * @param value
	 *            starion's delivery report value
	 * @return integer representing one of the {@link DeliveryReports.Starion}
	 *         constants.
	 */
	private int resolveDeliveryReport(String value) {

		if (value.equals( StarionDeliveryReportValues.CHARGED.getValue() )) {
			return DeliveryReports.DELIVERED;
		}

		else {
			return DeliveryReports.Starion.NOT_CHARGED;
		}

	}

	@Override
	public boolean canHandleBulkMessage(String receiver) {
		return receiver.matches( this.bulkReceiversRegEx );
	}

	public void setSmsIntercepters(List<SendSmsIntercepter> smsIntercepters) {
		this.smsIntercepters = smsIntercepters;
	}

	public void setBulkSender(BulkSender bulkSender) {
		this.bulkSender = bulkSender;
	}

	public void setProviderDAO(ProviderDAO providerDAO) {
		this.providerDAO = providerDAO;
	}

	public void setCore(Core core) {
		this.core = core;
	}

	public void setExceptionHandler(ExceptionHandler exceptionHandler) {
		this.exceptionHandler = exceptionHandler;
	}

	public void setSmsCenterUrl(String smsCenterUrl) {
		this.smsCenterUrl = smsCenterUrl;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public void setBulkReceiversRegEx(String bulkReceiversRegEx) {
		this.bulkReceiversRegEx = bulkReceiversRegEx;
	}

	public enum StarionDeliveryReportValues {

		CHARGED("OK"), NOT_CHARGED("NO");

		private String	value;

		private StarionDeliveryReportValues(String value) {
			this.value = value;
		}

		public String getValue() {
			return value;
		}
	}

	public enum StarionBulkDeliveryReportValues {
		DELIVERED("1"), UNDELIVERED("0");

		private String	value;

		private StarionBulkDeliveryReportValues(String value) {
			this.value = value;
		}

		public String getValue() {
			return value;
		}
	}

	/**
	 * Possible responses to the send sms request
	 */
	public enum SendSmsResponse {
		OK("OK"), NO("NO");

		private String	value;

		private SendSmsResponse(String value) {
			this.value = value;
		}

		public String getValue() {
			return value;
		}
	}

	/**
	 * Enumeration of Starion's send sms parameters which could be put into
	 * URL's query string in http get request.
	 * 
	 */
	public enum SendSmsParams {
		MSG_ID("msg_id"), CHARGE("charge"), TYPE("type"), MESSAGE("message");

		private SendSmsParams(String value) {
			this.value = value;
		}

		private String	value;

		public String getValue() {
			return value;
		}
	}

	/**
	 * Enumeration of starion's charging options, denoting should a message be
	 * charged or not.
	 * 
	 */
	public enum ChargingOptions {
		YES("OK"), NO("NO");

		private String	value;

		private ChargingOptions(String value) {
			this.value = value;
		}

		public String getValue() {
			return value;
		}
	}

	/**
	 * Enumeration of starion's message types;
	 * 
	 */
	public enum MessageType {

		TEXT("TEXT"), // sms text message
		WAP("WAP"); // wap push message

		private String	value;

		private MessageType(String value) {
			this.value = value;
		}

		public String getValue() {
			return value;
		}
	}

}
